Skip to content

Add URL Routing Support for MCP HTTP Transport #622

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

Destrayon
Copy link

@Destrayon Destrayon commented Jul 12, 2025

Adds URL routing support for MCP HTTP transport, enabling route-aware tool filtering where different tool sets can be exposed at different HTTP endpoints using the new [McpServerToolRoute] attribute.

Motivation and Context

Enables context-specific tool collections for multi-agent systems and organizational security boundaries. Currently, MCP servers expose all registered tools at a single HTTP endpoint, which limits flexibility in scenarios where different clients or contexts require access to different tool sets.

Our company's multi-agent system requires different tool sets for different agent types (research agents need web search tools, execution agents need file operation tools). This feature enables route-based tool filtering while maintaining backward compatibility.

Currently, MCP servers expose all registered tools at a single HTTP endpoint, forcing every client to see every tool regardless of context. This creates several problems:

  1. Multi-agent confusion - Different agent types (research, execution, admin) all see the same massive tool list, leading to inappropriate tool selection
  2. No logical separation - Administrative tools appear alongside general utilities, with no way to organize by context
  3. Deployment inflexibility - Companies can't selectively expose different tool sets to different internal applications
  4. Scalability issues - Large servers with hundreds of tools become unwieldy for clients to navigate

This feature enables route-based tool filtering (e.g., /mcp/admin, /mcp/utilities) while maintaining backward compatibility, addressing community requests for selective tool exposure and better multi-agent system support.

How Has This Been Tested?

  • Comprehensive unit tests in MapMcpRoutingTests.cs covering:

    • Route filtering with different tool counts per route
    • Global vs. specific route tool availability
    • Multi-route tool exposure across multiple endpoints
    • Trailing slash handling and path normalization
    • Tool invocation on specific routes
    • Backward compatibility with standard MapMcp()
  • Complete sample application UrlRoutingSseServer demonstrating:

    • 5 tool categories across multiple routes (admin, weather, math, echo, utilities)
    • Real HTTP endpoints with working MCP protocol
    • All route combinations functioning correctly
  • Manual testing with curl verifying:

    • /mcp returns all 9 tools (global route)
    • /mcp/admin returns 3 tools (admin + global tools only)
    • /mcp/weather returns 3 tools (weather + global tools only)
    • /mcp/math returns 3 tools (math + global tools only)
    • Tool calls work correctly on their designated routes
    • Unknown routes fall back to expected behavior
  • Backward compatibility verification:

    • Existing WithHttpTransport() and MapMcp() methods unchanged
    • All existing samples and tests continue to pass
    • No breaking changes to current MCP server implementations

Breaking Changes

None - fully backward compatible. All existing tests pass without modification. New functionality is opt-in via WithHttpTransportAndRouting() and MapMcpWithRouting().

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

Implementation Architecture

The routing system works through session-level tool filtering rather than endpoint-level routing:

  1. Route Discovery: During startup, MapMcpWithRouting() scans all registered tools for [McpServerToolRoute] attributes and creates separate HTTP endpoints for each discovered route
  2. Session Configuration: When a client connects to a specific route (e.g., /mcp/admin), the ConfigureSessionOptions callback filters the available tools before the session starts
  3. Tool Inheritance: Tools without route attributes are treated as "global" and appear on all routes, while route-specific tools only appear on their designated endpoints

Design Decision: Session-Level vs. Endpoint-Level Filtering

This implementation filters tools per-session rather than creating separate MCP server instances per route. Alternative approaches considered:

  • Multiple server instances: Would require duplicating server infrastructure for each route
  • Middleware-based filtering: Would require parsing and modifying MCP JSON-RPC response bodies (like tools/list responses) in middleware components before they're sent to clients, and rejecting unauthorized tools/call requests. This adds complexity to the request pipeline and requires handling JSON-RPC message parsing/modification.
  • Client-side filtering: Would expose all tools over the wire, requiring clients to handle filtering logic

Session-level filtering provides the cleanest separation while maintaining full backward compatibility and leveraging existing MCP session infrastructure.

Route Normalization

All route paths undergo consistent normalization (leading slash, trailing slash removal, duplicate slash collapsing) to ensure reliable matching regardless of how routes are specified in code vs. HTTP requests.

Tool Visibility Model

The implementation follows a inheritance-based tool visibility model:

  • Global route (/mcp): Exposes ALL registered tools regardless of route attributes
  • Specific routes (/mcp/admin, /mcp/weather): Expose only their route-specific tools PLUS all global tools
  • Global tools: Tools without [McpServerToolRoute] attributes appear on every route, providing common functionality across all contexts

Example with 9 total tools:

  • /mcp → 9 tools (2 admin + 2 weather + 2 math + 2 echo + 1 global)
  • /mcp/admin → 3 tools (2 admin + 1 global)
  • /mcp/weather → 3 tools (2 weather + 1 global)

This example is if the base route is set to "mcp".
This model ensures that essential tools (logging, diagnostics, etc.) remain available across all contexts while still enabling route-specific specialization.

Destrayon and others added 4 commits July 12, 2025 14:47
Implements URL-based tool filtering to enable context-specific tool collections
for multi-agent systems and specialized agent workflows.

Features:
- McpServerToolRouteAttribute for method-level route assignment
- WithHttpTransportAndRouting() extension for route-aware transport
- MapMcpWithRouting() for automatic route discovery and endpoint mapping
- RouteAwareToolService for path normalization and route management
- Session-based tool filtering preserving resources and prompts

Key capabilities:
- Global tools (no route attribute) available on all routes
- Route-specific tools only accessible on designated endpoints
- Multi-route tools available on multiple specified routes
- Backward compatibility with existing WithHttpTransport/MapMcp

Example usage:
[McpServerToolRoute("admin")]  // Available at /mcp/admin
[McpServerToolRoute("weather", "utilities")]  // Multi-route tool

Use cases:
- Multi-agent system coordination with specialized tool sets
- Agent workflow orchestration with phase-appropriate capabilities
- Context-aware tool separation for different agent types
- Secure agent collaboration through filtered tool access

Tests: Comprehensive test suite covering routing logic, edge cases,
and backward compatibility scenarios
@@ -64,6 +64,38 @@ public sealed class McpServerToolCreateOptions
/// </remarks>
public string? Title { get; set; }

private IReadOnlyList<string>? routes;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe better use as property?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can do, switched it to a property.

Comment on lines 27 to 30
<ItemGroup>
<Folder Include="Attributes\" />
</ItemGroup>

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe not necessary?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the folder.

-Removed Attributes folder
-Changed routes getter and setter to a Routes property
Change also made for consistency:
-Removed Services folder and namespace
-Moved RouteAwareToolService.cs out of the folder and namespace
-Removed reference to this namespace from McpRouteAwarenessExtension.cs
Copy link
Contributor

@halter73 halter73 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we'll probably end up closing this in favor of a smaller change that adds something like a MapMcp(string pattern, Action<McpServerOptions>) overload, but I'm curious to hear what others think.

/// For example, a route name "echo" will be accessible at /mcp/echo when the global
/// route is configured as "/mcp".
/// </remarks>
public IReadOnlyList<string> Routes { get; }
Copy link
Contributor

@halter73 halter73 Jul 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stephentoub @eiriktsarpalis @PederHP Do you have any thoughts on filtering which tools are available on which routes via an attribute?

I think having different routes for different sets of tools is an important scenario, but I don't know that attributes are the best way to achieve this.

Currently, you can already specify different tools for different routes using ConfigureSessionOptions which this PR uses to implement the functionality of this attribute under the covers, but that's not the most convenient thing if you're using attributes WithToolsFromAssembly.

I think a middle ground might be to add an MapMcp(string pattern, Action<McpServerOptions>) overload.

MapMcp(string pattern, Action<IMcpServerBuilder>) would probably be even more convenient, but that won't work when MapMcp is called, because the service provider has already been built at that point.

/// </para>
/// </remarks>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="endpoints"/> is null.</exception>
public static IEndpointConventionBuilder MapMcpWithRouting(this IEndpointRouteBuilder endpoints, [StringSyntax("Route")] string pattern = "")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should add a new MapMcpWithRouting method. If we want to support routing via attributes, we should support that via the normal MapMcp method.

// Map MCP endpoints for all discovered routes
foreach (var discoveredRoute in routeService.OtherRoutes)
{
endpoints.MapMcp(discoveredRoute);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should be adding any routes that are not specified via the pattern argument. If we use attributes, it should only be used to filter which tools are available on routes that were already registered explicitly.

@Destrayon
Copy link
Author

That's all fine with me depending on what you decide. I wanted to throw an idea out there that felt like typical Microsoft magic, but if the maintainers decide on a specific implementation to this I would definitely love to take on the responsibility of putting it together if that is possible. Working on this was honestly a great experience :).

@echapmanFromBunnings
Copy link

I built a tool filtering extension to handle this. So it's multiple implementations of the same MCP in the config, with a different header for each set of tools.

Been meaning to extend it to resources and prompts also, so that'll be next.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants